package DBIx::Class::ResultSet::DataValidator;

use strict;
use warnings;

use base qw/
    DBIx::Class::ResultSet
/;

use Carp qw/ croak /;

our $VERSION = '0.05';

=head1 NAME

DBIx::Class::ResultSet::DataValidator - Data validation for create and update_all


=head1 DESCRIPTION

Data validation directly in the Model. This model is for create and update_all methods only.

See L<DBIx::Class::DataValidator> for more info..


=head1 ATTENTION

Dont forget, you have to do BOTH: use the DBIx::Class::ResultSet::DataValidator AND the DBIx::Class::DataValidator!! One of them wont work (actually the DBIx::Class::DataValidator alone may work but not a godd idea)

=cut

use DBIx::Class::DataValidator;
use Scalar::Util qw/ blessed  /;
use Clone qw/ clone /;


__PACKAGE__->mk_classdata( $_ ) for qw/
    _runtime_validation_methods
/;


=head1 METHODS


=head2 create_validated

performs the create method, but runs validate before! If validation fails it will die (with a DBIx::Class::DataValidator::CreateException-Exception)

    # uses "default" and "create" validator
    $schema->resultset( 'MyModel' )
        ->create_validated( $data_ref );
    
    # uses given valiadtors
    $schema->resultset( 'MyModel' )
        ->create_validated( $data_ref, @validators );

=cut

sub create_validated {
    my ( $self, $data_ref, @validators ) = @_;
    my $self_class = $self->result_class;
    
    # add the schema as arg to Data::FormValidator if this provides the args extensions
    $self_class->_data_form_validator->add_args( {
        schema => $self->result_source->schema
    } ) if $self_class->_data_form_validator && $self_class->_data_form_validator->can( 'add_args' );
    
    # set the validators
    if( ! @validators && $self_class->_validation_profiles ) {
        push @validators, 'default' if defined $self_class->_validation_profiles->{ default };
        push @validators, 'create' if defined $self_class->_validation_profiles->{ create };
    }
    
    # double hack:
    #   for older DBIx::Class versions check the storage and set the result source as instance unless defined
    #   for newer versions this causes an exception -> catch and set result source
    eval {
        $self_class->result_source_instance( $self->result_source )
            unless ref( $self_class->result_source_instance->storage );
    };
    $self_class->result_source_instance( $self->result_source ) if $@;
    
    # validate
    eval {
        $data_ref = $self_class->_validate_all( $data_ref, { die_ok => 1 }, @validators );
    };
    
    # check for erros
    if ( my $e = $@ ) {
        die DBIx::Class::DataValidator::CreateException->new( $e ) unless blessed( $e );
        die bless $e, "DBIx::Class::DataValidator::CreateException";
    }
    
    # create the object now
    my $object;
    eval {
        $object = $self->create_not_validated( $data_ref );
    };
    
    # oops, error in creation
    if ( my $e = $@ ) {
        die DBIx::Class::DataValidator::CreateException->new( $e );
    }
    
    # return the just created object
    return $object;
}


=head2 create_not_validated

Call the create method with assured disabled validation 

=cut

sub create_not_validated {
    my ( $self, $data_ref ) = @_;
    my $self_class = $self->result_class;
    $self_class->_disable_native_validation_temp( 1 );
    my $res;
    eval { # assure native validation disabling will be switched of aftewards!
        $res = $self->create( $data_ref );
    };
    my $err = $@;
    $self_class->_disable_native_validation_temp( 0 );
    croak $err if $err;
    return $res;
}


=head2 create

Modified create. If you have disabled native validation it simply calls the regular
update method. If native validation is not disabled, it will call the create_validated method
instead.

    # in your model
    __PACKAGE__->disable_native_validation(0);
    
    # somewhere else
    my $object = $model->create( $data_ref, qw/ validator1 validator2 / );
    
    # would be the same as
    my $object = $model->create_validated( $data_ref, qw/ validator1 validator2 / );
    
    # call direct create method withouth validation anyway
    my $object = $model->create_not_validated( $data_ref );

=cut

sub create {
    my ( $self, $data_ref, @validators ) = @_;
    my $self_class = $self->result_class;
    if ( $self_class->_disable_native_validation_temp || $self_class->disable_native_validation ) {
        return $self->next::method( $data_ref );
    }
    else {
        return $self->create_validated( $data_ref, @validators );
    }
}


=head2 update_all_validated

updates multiple objects using theire SINGLE update-method! This is far slower then
the actual update_all method, but each and every object will be validated. Use with
caution.

    my $result = MySchema::MyModel->search( .. );
    $result->update_all_validated( { field1 => 'ok' } ); # uses "default" and "update" validator ..
    $result->update_all_validated( { field1 => 'ok' }, qw/ other / ); # uses only "other" validator ..

=cut

sub update_all_validated {
    my ( $self, $all_data_ref, @validator_profiles ) = @_;
    
    # reset at begin of list
    $self->reset;
    
    # go through all ..
    while( my $n = $self->next ) {
        
        # .. and update each validated
        $n->update_validated( clone( $all_data_ref ), @validator_profiles );
    }
    
    # then reset again
    $self->reset;
    
    # return for chainging
    return $self;
}


=head2 update_all_not_validated

Call he update_all with assured disabled validation

=cut

sub update_all_not_validated {
    my ( $self, $all_data_ref ) = @_;
    
    
    # reset at begin of list
    $self->reset;
    
    # go through all ..
    while( my $n = $self->next ) {
        
        # .. and update each NOT validated
        $n->update_not_validated( clone( $all_data_ref ) );
    }
    
    # then reset again
    $self->reset;
    
    # return for chainging
    return $self;
}


=head2 update_all

rewrite the update_all method to be validation aware if the disable_native_validation
flag has not been set.

=cut

sub update_all {
    my ( $self, $all_data_ref, @validators ) = @_;
    my $self_class = $self->result_class;
    unless ( $self_class->disable_native_validation ) {
        return $self->update_all_validated( $all_data_ref, @validators );
    }
    else {
        return $self->next::method( $all_data_ref );
    }
}


=head2 validates

Call the validates method as defined in your DBIx::Class .. See L<DBIx::Class::Component::DataValidator>::validate

    # with default or only column validators:
    my $res = $schema->resultset( 'MyModel' )->validates( $data_ref );
    $schema->resultset( 'MyModel' )->update_or_create( $res->data ) unless $res->error;
    
    # now with custom + column validators
    my $res2 = $schema->resultset( 'MyModel' )->validates( $data_ref, qw/ validator1 validator2 / );
    $schema->resultset( 'MyModel' )->update_or_create( $res2->data ) unless $res2->error;

If you provide an existing (or possible existing object) as the second parameter, it will use the "validates_for_update"-method instead

    # read the object
    my $object = $schema->resultset( 'MyModel' )->find( $some_key );
    
    # now $object either exists or is undef
    my $res = $schema->resultset( 'MyModel' )->validates( $data_ref, $object, qw/ validator1 validator2 / );
    $schema->resultset( 'MyModel' )->update_or_create( $res->data ) unless $res->error;

=cut

sub validates {
    my ( $self, $all_data_ref, @validator_profiles ) = @_;
    my $self_class = $self->result_class;
    
    # determine validate method and try to extract object ..
    my $method = 'validates';
    if ( @validator_profiles && ! defined $validator_profiles[0] || ref( $validator_profiles[0] ) && blessed( $validator_profiles[0] ) ) {
        my $object = shift @validator_profiles;
        if ( $object ) {
            $method .= '_for_update';
            $self_class = $object;
        }
    }
    
    # otherwise, set the schema..
    else {
        # add the schema as arg to Data::FormValidator if this provides the args extensions
        $self_class->_data_form_validator->add_args( {
            schema => $self->result_source->schema
        } ) if $self_class->_data_form_validator && $self_class->_data_form_validator->can( 'add_args' );
    }
    
    return $self_class->$method( $all_data_ref, @validator_profiles );
}


=head2 set_runtime_validation_methods

Sets validation methods at runtime. They will be used in any future create, update or update_all call.

!! CAREFULL! remove them with clear_runtime_validation_methods !!

=cut

sub set_runtime_validation_methods {
    my ( $self, $methods_ref ) = @_;
    $methods_ref ||= {};
    my $self_class = $self->result_class;
    $self_class->_runtime_validation_methods( $methods_ref );
    return $self;
}


=head2 clear_runtime_validation_methods

Clears all runtime validation methods .. same as calling set without params.

=cut

sub clear_runtime_validation_methods {
    my ( $self ) = @_;
    my $self_class = $self->result_class;
    $self_class->_runtime_validation_methods( {} );
    return $self;
}


=head1 SEE ALSO

L<Data::DBIx::Class>
L<Data::DBIx::Class::Results>
L<Data::DBIx::Class::Schema>
L<Data::FormValidator>
L<Data::FormValidator::Results>

=head1 AUTHOR

Ulrich Kautz - uk@fortrabbit.de

=cut


1;